from django.db import models

from inv.utils.trace    import *
from inv.utils.errors   import *
from inv.utils.db       import *
from inv.settings       import *

class User_Group(models.Model):
    """This class stores the list of groups to which a user can belong.
    It is also linked to the access and privileges provided against the
    Frontend and Backend fields
    """
    nam = models.CharField(max_length=128, unique=True)
    
    def trace(self):
        """Tracing function to print the name of the Class
        """
        return "[" + "nam = " + str(self.nam) + "]"

    def refine(self):
        """Refines the user provided data to one that can inserted into database 
        column. It behooves on caller to handle the ValueError Exception in case
        of int and float datatypes 
        """
        self.nam = refine_col_val("CHAR", self.nam)

    def add(self) :
        """This is the entry point for any insertion that happens to the class 
        table. 
        """
        ret = err[7][0]
        try :
            self.refine()
            if self.nam == None :
                #Empty class name
                ret = err[7][1]
            else :
                self.save()
        except IntegrityError :
            #Class already exists
            ret = err[7][2]
        ADM.debug(self.trace() + " " + ret['rm'], extra={'cls':self})
        return ret


class Class(models.Model) :
   """ This is the main Class that defines an Entity and its structure. The 
   nam could be Product, Vendor etc.
   | id | nam | enb |
   """
   
   nam = models.CharField(max_length=128, unique=True)
   """Name of the Class
   """
   
   enb = models.BooleanField()
   """Hide/Unhide the particular Class. All the groups and metas under this
   class would be marked accordingly
   """
   
   def trace(self):
      """Tracing function to print the name of the Class
      """
      return "[" + "nam = " + str(self.nam) + ", enb = " + str(self.enb) + "]"

   def refine(self):
      """Refines the user provided data to one that can inserted into database 
      column. It behooves on caller to handle the ValueError Exception in case
      of int and float datatypes 
      """
      self.nam = refine_col_val("CHAR", self.nam)

   def add(self) :
      """This is the entry point for any insertion that happens to the class 
      table. 
      """
      ret = err[1][0]
      try :
         self.refine()
         if self.nam == None :
            #Empty class name
            ret = err[1][1]
         else :
            self.save()
      except IntegrityError :
         #Class already exists
         ret = err[1][2]
      ADM.debug(self.trace() + " " + ret['rm'], extra={'cls':self})
      return ret

class Class_Group(models.Model) :
   """This class is used only to group the meta columns of a particular class
   under different group to display in the UI. It also controls the sequencing
   of groups in the UI
   | id | nam | cls_id | seq | enb |
   """
   
   nam = models.CharField(max_length=128)
   """Name of the Group
   """
   
   cls_id = models.IntegerField()
   """class ID to map to Class. Cannot be updated once added
   """
   
   seq = models.IntegerField(null=True)
   """Used to sequence the Group displayed on UI
   """
   
   enb = models.BooleanField()
   """Hide/Unhide the particular Class Group . All the metas under this
   class group would be marked accordingly. However, the value cannot be
   true if the parent class is marked as False.
   """
   
   class Meta:
      unique_together = (('nam', 'cls_id'),)

   def trace(self):
      """Tracing function to print the group name and class id corresponding
      to the class to which the group belongs
      """       
      return "[" + "nam = " + str(self.nam) \
         + ", class_id = " + str(self.cls_id) \
         + ", seq = " + str(self.seq) \
         + ", enb = " + str(self.enb) + "]"

   def refine(self):
      """Refines the user provided data to one that can inserted into database 
      column. It behooves on caller to handle the ValueError Exception in case
      of int and float datatypes 
      """
      self.nam = refine_col_val("CHAR", self.nam)

   def check_dependency(self):
      """Verify of the Class ID provided by the user exists in the Class table.
      Also verifies if the Class is enabled if the group being added is marked
      as enabled
      """
      cls = Class.objects.get(id=self.cls_id)
      if (cls.enb == False) and (self.enb == True) :
         raise AssertionError

   def add(self) :
      """This is the entry point for any insertion that happens to the class 
      Group table.
      """
      ret = err[2][0]
      try :
         self.check_dependency()
         self.refine()

         if self.nam == None :
            #Empty group name
            ret = err[2][1]
         else :
            self.save()

      except IntegrityError :
         #Group name already exists
         ret = err[2][2]
      except Class.DoesNotExist, (detail) :
         #Class does not exists
         ret = err[2][3]
      except AssertionError :
         #Class not enabled. Trying to enable group
         ret = err[2][4]
      ADM.debug(self.trace() + " " + ret['rm'], extra={'cls':self})
      return ret

class Class_Meta(models.Model):
   """This class defines the various fields and its types that would appear in
   the UI screen for a particular entity. It holds both its parent group ID 
   and grand parent class id. Eventually, Only Class_id, Meta_id and Object_id 
   would be used to store any instance of an Entity. It also contols the 
   sequencing of meta within a particular group
   | id | nam | grp_id | cls_id | typ | seq | enb | ptn_id | sel_id |
   """
   
   nam = models.CharField(max_length=128)
   """Name of the Meta. Appears in UI screen
   """
   
   grp_id = models.IntegerField()
   """group ID to map to the Class_Group. Can be updated to different group 
   of the same class
   """
   
   cls_id = models.IntegerField()
   """class ID to map to Class. Cannot be updated once added
   """
   
   typ = models.CharField(max_length=64)
   """Meta type like INT, CHAR etc. Cannot be updated once added.
   See INV_SUPPORTED_FIELD_TYPE in settings.py for complete list
   """
   
   seq = models.IntegerField(null=True)
   """Used to sequence the Meta display on UI
   """
   
   enb = models.BooleanField()
   """Hide/Unhide the particular Class Meta. However, the value cannot be
   true if the parent class or the group is marked as False.
   """
   
   ptn_id = models.IntegerField(null=True)
   """Incremental value deduced separately for each meta type to ensure
   uniform distribution across the Entity Meta table partitions
   """
   
   sel_id = models.IntegerField(null=True)
   """Meta id of the selection field for NTO1 and NTOM types.
   during the interlink. This column is null for other meta types.
   This meta id has to be that of a CHAR_UNIQ type
   """
   
   class Meta:
      unique_together = (('nam', 'cls_id'),)

   def trace(self):
      """Tracing function to print the meta name, group ID that maps to 
      Group name and class ID that maps to the class name.
      """
      return "[" + "nam = " + str(self.nam) \
         + ", group_id = " + str(self.grp_id) \
         + ", cls_id = " + str(self.cls_id) \
         + ", type = " + str(self.typ) \
         + ", enb = " + str(self.enb) \
         + ", seq = " + str(self.enb) \
         + ", ptn_id = " + str(self.ptn_id) + \
          ", sel_id = " + str(self.sel_id) + "]"

   def refine(self):
      """Refines the user provided data to one that can inserted into database 
      column. It behooves on caller to handle the ValueError Exception in case
      of int and float datatypes. It also checks the validity of the field type
      and raises an Assertion Error it does not belong to supported field type
      """
      self.nam = refine_col_val("CHAR", self.nam)
      self.typ = refine_col_val("CHAR", self.typ)
      self.ptn_id = Class_Meta.objects.filter(typ=self.typ).count()
      if (self.sel_id == '') :
         self.sel_id = None
      else :
         self.sel_id = int(self.sel_id)

      if self.typ not in INV_SUPPORTED_FIELD_TYPE :
         raise AssertionError(4, 'Invalid Meta type')

   def check_dependency(self):
      """Verify of the Class ID and Group ID provided by the user exists in 
      the Class and Group table respectively. Also verifies if the group is
      enabled in case the meta being added is marked enabled.
      """
      grp = Class_Group.objects.get(id=self.grp_id)
      self.cls_id = grp.cls_id
      if (grp.enb == False) and (self.enb == True) :
         raise AssertionError(5, 'Group not enabled')

   def add(self) :
      """This is the entry point for any insertion that happens to the class 
      Meta table.
      """
      ret = err[3][0]
      try :
         self.check_dependency()

         self.refine()

         if self.nam == None :
            ret = err[3][1]
         else :
            self.save()

      except IntegrityError :
      #Meta name already exists under the class id.
         ret = err[3][2]
      except Class_Group.DoesNotExist, (detail) :
      #Class group does not exists
         ret = err[3][3]
      except AssertionError, (errno, details) :
         #errno = 4, Invalid Meta type
         #errno = 5, Group not enabled
         ret = err[3][errno]
      ADM.debug(self.trace() + " " + ret['rm'], extra={'cls':self})
      return ret

class Class_Interlink(models.Model):
   """This class contains the list of other meta ids that would be
   visible along with selection field during class interlink
   """
   
   met_id = models.IntegerField()
   """Meta id of the Nto1 or NTOM meta types.
   """
   
   vis_id = models.IntegerField()
   """Meta ids of the inter linked class that should be shown
   along with the seletion field
   """
   
   class Meta:
      unique_together = (('met_id', 'vis_id'),)
